<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <style>
    div {
      background-color: #666;
      height: 300px;
    }
  </style>
  <body style="height: 2000px;">
    <div></div>
  </body>
  <script>
    let dom = document.getElementsByTagName("div")[0];
    dom.onmousemove = debounce(listenMoveOn, 1000, false);

    function listenMoveOn(...args) {
      // 监听的回调做一些事情
      console.log("this", this);
      console.log("args", args);
    }
    /**
     *
     * @param {function} fn - 回调函数
     * @param {number} delay - 等待时间
     * @param {boolean} immediate - 是否立即执行
     */
    function debounce(fn, delay, immediate = false) {
      let timer, timestamp, context, param;

      const later = function() {
        let last = +new Date() - timestamp  // 距离上一次操作的时间间隔ms
        console.log('last', last)
        if(last < delay && last >= 0) {
            timer = setTimeout(later, delay) // 重置定时器
        } else {
            timer = null
            if (!immediate) {
                fn.apply(context, param)
                context = param = null
            }
        }
      }

      return function (...args) {
        context = this
        param = args
        timestamp = +new Date()
        let callNow = immediate && !timer   // 当 immediate 设置true，并且timer还没有被赋值时，说明刚进来，可进行立刻调用
        if (!timer) timer = setTimeout(later, delay)
        if (callNow) {
            fn.apply(context, args)
        }
      };
    }
    function debounce2(func, wait, immediate = false) {
      let timeout, args, context, timestamp, result;
      const later = function () {
        // 据上一次触发时间间隔
        const last = +new Date() - timestamp;
        console.log('last', last)

        // 上次被包装函数被调用时间间隔 last 小于设定时间间隔 wait
        if (last < wait && last > 0) {
          timeout = setTimeout(later, wait - last);
        } else {
          timeout = null;
          // 如果设定为immediate===true，因为开始边界已经调用过了此处无需调用
          if (!immediate) {
            result = func.apply(context, args);
            if (!timeout) context = args = null;
          }
        }
      };

      return function (...args) {
        context = this;
        timestamp = +new Date();
        // console.log('timestamp', timestamp)
        const callNow = immediate && !timeout;   // 第一次进来timeout为undefined
        // 触发时如果延时不存在，重新设定延时
        if (!timeout) timeout = setTimeout(later, wait);
        if (callNow) {
          result = func.apply(context, args);
          context = args = null;
        }
        return result;
      };
    }
  </script>
  <!-- <script>
    let dom = document.getElementsByTagName("div")[0];
    // dom.onmousemove = listenMoveOn;
    dom.onmousemove = debounce(listenMoveOn, 200);

    function listenMoveOn(...param) {
      console.log("this", param);
      //   console.log("scrollTop", this);
    }
    function debounce(func, await) {
      // console.log('this', this)
      var timer = null;
      // 这里只调用一次（执行一次）因为在window环境中调用，所以指向window
      return function (...args) {
        //   后续鼠标移动,都是要通过这个函数去调用func,所以这个函数相当于是绑定事件对象的函数,拿到的this就是dom对象
        //  this  指向dom元素

        //   如果直接这样去调用func,相当于window.setTimeout, 此时timeout函数体内的this指向window

        //  使用 apply 之后 把func的this指向改为当前的this指向,this指向得到修正修正
        //   console.log("this", args);

        // 传参使用扩展运算符进行参数传递

        // 立即执行
        clearTimeout(timer);
        setTimeout(func.apply(this, args), await);
      };
    }
    // window.onscroll = listenScroll;
    // window.onscroll = debounce(listenScroll, 200);
  </script> -->
</html>
